#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <sys/time.h>
#include <signal.h>

#include "../Sock/errno.hpp"
#include "../Sock/Logmessage.hpp"
#include "../Sock/Socket.hpp"
namespace Client
{
    using namespace std;
    static const int g_bufSize = 1024;
    static const int g_packLen = 56;
    static long g_timeSum = 0;
    int sendNum = 0; // 发送包计数
    int recvNum = 0; // 接收包计数
    std::string g_domain_name;
    class icmpClient
    {
        int sockfd_;
        string serverip_;

        char *sendPackBuf; // 发送包缓冲区
        char *recvPackBuf; // 接收包缓冲区

    public:
        icmpClient(const string &domainName)
            : sockfd_(-1)
        {
            g_domain_name = domainName;
            // 根据域名获取ip地址
            serverip_ = Socket::getIpByDomainName(domainName);
            logMessage(NORMAL, "server_ip %s", serverip_.c_str());
            sendPackBuf = new char[g_bufSize];
            recvPackBuf = new char[g_bufSize];
        }
        void initClient()
        {
            // 域间套接字
            sockfd_ = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
            if (sockfd_ == -1)
            {
                logMessage(FATAL, "socket err ");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL, "initClient success...");
        }

        void run()
        {
            struct sockaddr_in server; // 目的Ip+port
            memset(&server, 0, sizeof(server));

            server.sin_family = AF_INET;
            // 主机序列转网络序列
            server.sin_addr.s_addr = inet_addr(serverip_.c_str());

            pid_t pid = getpid();

            printf("PING %s (%s) %d bytes of data\n", serverip_.c_str(), inet_ntoa(server.sin_addr), g_packLen);

            // 设置信号捕捉方法：捕捉2号信号，交由handler函数自定义处理
            signal(SIGINT, handler);

            while (true)
            {
                // 发送包
                sendPack(pid, server);
                // 接受解析响应
                recvPack();
                sleep(1);
            }
        }

    private:
        static void handler(int signo)
        {
            printf("--- %s ping statistics ---\n", g_domain_name.c_str());
            printf("%d packets transmitted, %d received, %.2f%% packet loss, time %ldms\n", 
                    sendNum, recvNum, ((double)(sendNum - recvNum) / sendNum)*100, g_timeSum
            );
            exit(0);
        }
        u_int16_t chksum(u_int16_t *addr, int len)
        {
            unsigned int ret = 0;

            while (len > 1)
            {
                ret += *addr++;
                len -= 2;
            }
            if (len == 1)
            {
                ret += *(unsigned char *)addr;
            }

            ret = (ret >> 16) + (ret & 0xffff);
            ret += (ret >> 16);

            return (u_int16_t)~ret;
        }

        int makePack(int num, pid_t pid)
        {
            memset(sendPackBuf, 0, g_bufSize);

            struct icmp *tmp = reinterpret_cast<struct icmp *>(sendPackBuf);
            tmp->icmp_type = ICMP_ECHO; // 类型：请求回显
            tmp->icmp_code = 0;         // 代码（0表示请求）
            tmp->icmp_seq = num;        // 序列号 填写发送包序号
            tmp->icmp_id = pid;         // 标识符  进程id

            // 获取当前时间，并存入data字段
            struct timeval tval;
            gettimeofday(&tval, nullptr);
            memcpy((void *)tmp->icmp_data, (void *)&tval, sizeof(tval));
            // 生成校验码，ICMP报头固定8字节
            tmp->icmp_cksum = chksum((u_int16_t *)sendPackBuf, g_packLen + 8);
            return g_packLen + 8;
        }
        void sendPack(pid_t pid, struct sockaddr_in svr)
        {
            // 发送包编号++
            sendNum++;
            int res = makePack(sendNum, pid);

            sendto(sockfd_, sendPackBuf, res, 0, (struct sockaddr *)&svr, sizeof(svr));
        }

        void recvPack()
        {
            memset(recvPackBuf, 0, g_bufSize);
            struct sockaddr_in response;
            socklen_t len = sizeof(response);
            // 接受包编号++
            recvNum++;
            ssize_t res = recvfrom(sockfd_, recvPackBuf, g_bufSize, 0, (struct sockaddr *)&response, &len);
            if (res < 0)
            {
                logMessage(ERROR, "icmp recvform err");
                exit(RECV_ERR);
            }
            struct in_addr n_ip = response.sin_addr;
            parseICMP(n_ip);
        }

        void parseICMP(struct in_addr &n_ip)
        {
            // 解析ICMP包
            // 1.先转化成IP数据包
            struct ip *t_ip = reinterpret_cast<struct ip *>(recvPackBuf);

            // 2.根据IP首部长度*4=ip报头总长度,偏移到ICMP首部
            char *buf = recvPackBuf; //!!!!!!!!!!!
            struct icmp *t_icmp = reinterpret_cast<struct icmp *>(buf + (t_ip->ip_hl << 2));
            struct timeval onePackOver;
            gettimeofday(&onePackOver, nullptr);

            // 获取请求-响应时间差
            long diff = diffTime(&onePackOver, (struct timeval *)(t_icmp->icmp_data));
            g_timeSum += diff;

            printf("%d bytes from %s: icmp_seq=%d ttl=%d time=%ld ms\n",
                   g_packLen, inet_ntoa(n_ip), t_icmp->icmp_seq,
                   t_ip->ip_ttl, diff);
        }

        // 计算时间差
        long diffTime(struct timeval *end, struct timeval *start)
        {
            return (end->tv_sec - start->tv_sec) * 1000 + (end->tv_usec - start->tv_usec) / 1000;
        }

    public:
        ~icmpClient()
        {
            delete[] recvPackBuf;
            delete[] sendPackBuf;
            // 关闭套接字
            close(sockfd_);
        }
    };
}
